#include <codex/driver/CDX_Driver_Opengles.h>
//#include <codex/os/types/CDX_Types.h>
//#include "PE_ResourceMgr.h"
namespace codex
{
namespace video
{

	NativeWindowType driver::m_windowType = NULL;
	NativeDisplay driver::m_display = NULL;
	NativeSurface driver::m_surface = NULL;
	NativeContext driver::m_context = NULL;
	tU32 driver::m_depthBufferID = 0;
	tU32 driver::m_stencilBufferID = 0;

	void driver::beginScene( void )
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
	}

	void driver::endScene( void )
	{
		eglSwapBuffers(m_display, m_surface);
	}


	tBOOL driver::createDriver( NativeWindowType winType )
	{

		EGLint numConfigs;
		EGLint majorVersion;
		EGLint minorVersion;
		EGLConfig config;
		EGLint contextAttribs[] = { EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE, EGL_NONE };
		
		/*
		EGLint attribList[] =
		{
			EGL_RED_SIZE,       5,
			EGL_GREEN_SIZE,     6,
			EGL_BLUE_SIZE,      5,
			EGL_ALPHA_SIZE,     (flags & ES_WINDOW_ALPHA) ? 8 : EGL_DONT_CARE,
			EGL_DEPTH_SIZE,     (flags & ES_WINDOW_DEPTH) ? 8 : EGL_DONT_CARE,
			EGL_STENCIL_SIZE,   (flags & ES_WINDOW_STENCIL) ? 8 : EGL_DONT_CARE,
			EGL_SAMPLE_BUFFERS, (flags & ES_WINDOW_MULTISAMPLE) ? 1 : 0,
			EGL_NONE
		};
		*/

		EGLint attribList[] =
		{
			EGL_RED_SIZE,       5,
			EGL_GREEN_SIZE,     6,
			EGL_BLUE_SIZE,      5,
			EGL_ALPHA_SIZE,     EGL_DONT_CARE,
			EGL_DEPTH_SIZE,     EGL_DONT_CARE,
			EGL_STENCIL_SIZE,   EGL_DONT_CARE,
			EGL_SAMPLE_BUFFERS, 0,
			EGL_NONE
		};

		// Get Display
		m_display = eglGetDisplay(GetDC(winType));
		if ( m_display == EGL_NO_DISPLAY )
		{
			printf("ERROR: No Display\n");
			return FALSE;
		}

		// Initialize EGL
		if ( !eglInitialize(m_display, &majorVersion, &minorVersion) )
		{
			printf("ERROR: Cannot initialize\n");
			return FALSE;
		}
		printf("Display Version: %d.%d\n", majorVersion, minorVersion);

		// Get configs
		if ( !eglGetConfigs(m_display, NULL, 0, &numConfigs) )
		{
			printf("ERROR: Display cannot be configured\n");
			return FALSE;
		}
		printf("Configuration Numbers: %d\nChoosing Config\n", numConfigs);

		// Choose config
		if ( !eglChooseConfig(m_display, attribList, &config, 1, &numConfigs) )
		{
			printf("ERROR: Cannot choose config\n");
			return FALSE;
		}

		// Create a surface
		printf("Creating surface.\n");
		m_surface = eglCreateWindowSurface(m_display, config, winType, NULL);
		if ( m_surface == EGL_NO_SURFACE )
		{
			printf("ERROR: Cannot Create Window surface\n");
			return FALSE;
		}

		// Create a GL context
		printf("Creating Context.\n");
		m_context = eglCreateContext(m_display, config, EGL_NO_CONTEXT, contextAttribs );
		if ( m_context == EGL_NO_CONTEXT )
		{
			printf("ERROR: Cannot create context\n");
			return FALSE;
		}   

		// Make the context current
		if ( !eglMakeCurrent(m_display, m_surface, m_surface, m_context) )
		{
			printf("ERROR: Cannot make current\n");
			return FALSE;
		}
		printf("Done.\n");

		// other configs
		setupDriver();

		return TRUE;
	}
/*
	tU32 driver::loadShader(GLenum type, const char *name, const int* l)
	{
		tU32 shader = glCreateShader( type );

		glShaderSource( shader, 1, &name, l );

		glCompileShader( shader );

		tBOOL bCompiled = FALSE;

		glGetShaderiv( shader, GL_COMPILE_STATUS, &bCompiled );

		if( !bCompiled )
		{
			int leng = 0;
			glGetShaderiv( shader, GL_INFO_LOG_LENGTH, &leng );
			if( leng > 1 )
			{

				char *strBuffer = new char[leng];
				glGetShaderInfoLog( shader, leng, NULL, strBuffer );
				printf("-----------------------------------\n");
				printf("Error: Not compiled shader: \"%s\".\nreason: \n%s\n", (type==GL_VERTEX_SHADER)?"VERTEX":"FRAGMENT", strBuffer );
				printf("-----------------------------------\n");
				delete strBuffer;
				glDeleteShader( shader );
				return 0;
			}
		}

		return shader;
	}

	tU32 driver::setupShader(const char* vertexShaderSrc, const char* fragmentShaderSrc)
	{

		tU32 vsh = 0;
		tU32 fsh = 0;
		
		if( !( vsh = loadShader( GL_VERTEX_SHADER, vertexShaderSrc, NULL ) )
		{
			printf("ERROR: Cannot load Vertex shader: \"%s\".", );
			return = 0;
		}

		if( !( fsh = loadShader( GL_FRAGMENT_SHADER, fragmentShaderSrc, NULL ) )
		{
			printf("ERROR: Cannot load Fragmet shader: \"%s\".", );
			glDeleteShader(vsh);
			return = 0;
		}

		tU32 program = glCreateProgram();

		glAttachShader( program, vsh );
		glAttachShader( program, fsh );

		glLinkProgram( program );

		tBOOL bCompiled = FALSE;

		glGetProgramiv( program, GL_LINK_STATUS, &bCompiled );

		if( !bCompiled )
		{
			int leng = 0;
			glGetProgramiv( program, GL_INFO_LOG_LENGTH, &leng );
			if( leng > 1 )
			{
				char *strBuffer = new char[leng];
				glGetProgramInfoLog( program, leng, NULL, strBuffer );
				printf("Error: Not linking. reason: \n%s\n", strBuffer );
				delete strBuffer;
				glDeleteProgram( program );
				glDeleteShader(vsh);
				glDeleteShader(fsh);
				return 0;
			}
		}

		glDeleteShader(vsh);
		glDeleteShader(fsh);

		return program;
	}

*/
	tU32 driver::createBuffer(GLenum target, void *data, tU32 elementSize, tU32 count)
	{
		tU32 buffer = 0;
		//generate buffer
		glGenBuffers( 1, &buffer );
		//set newlly generated buffer as current
		glBindBuffer( target, buffer );
		//load data to buffer
		glBufferData( target, elementSize * count, data, GL_STATIC_DRAW );
		// return buffer
		return buffer;
	}

	void driver::destroyBuffer( tU32* buffer )
	{
		glDeleteBuffers( 1, buffer );
	}

	tU32 driver::createVertexBuffer( void* data, tU32 size, tU32 count )
	{
		return createBuffer( GL_ARRAY_BUFFER, data, size, count );
	}

	tU32 driver::createIndexBuffer( void* data, tU32 size, tU32 count )
	{
		return createBuffer( GL_ELEMENT_ARRAY_BUFFER, data, size, count );
	}

	void driver::setupDriver( void )
	{
		glEnable(GL_DEPTH_TEST);

		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glClearColor ( 0.3f, 0.6f, 0.9f, 0.0f );
	}

	void driver::release( void )
	{

	}
	
	//=================================================================================================================
	// Summary: create a texture
	// @data: data of the given texture
	// @width: width of the texture
	// @height: height of the texture
	// @mipmaps: mipmap count for this texture
	// @format: pixel format of the texture
	// @compressed: some platform can load compressed data
	//=================================================================================================================
	tU32 driver::createTexture( const void* data, tU32 width, tU32 height, tU32 bpp, Image::EFormat format, Image::EPixelDataType type, int mipmaps )
	{
		tU32 tex;
		GLenum dataType;
		GLenum dataFormat;
		int level = 0;
		//tBOOL compressed = FALSE; 

		// generates the image
		glGenTextures( 1, &tex );
		glBindTexture( GL_TEXTURE_2D, tex );


		// TODO: if format is PVRTC 2 or 4, add support for compressed data.
		switch(format)
		{
		case Image::eFMT_ALPHA: dataFormat = GL_ALPHA; break;
		case Image::eFMT_LUMINANCE: dataFormat = GL_LUMINANCE; break;

		case Image::eFMT_LUMINANCE_ALPHA: dataFormat = GL_LUMINANCE_ALPHA; break;
		case Image::eFMT_RGB: dataFormat = GL_RGB; break;
		case Image::eFMT_RGBA: dataFormat = GL_RGBA; break;
		}
		
		switch(type)
		{
		case Image::ePDT_UBYTE: dataType = GL_UNSIGNED_BYTE; break;
		case Image::ePDT_USHORT_4444: dataType = GL_UNSIGNED_SHORT_4_4_4_4; break;
		case Image::ePDT_USHORT_5551: dataType = GL_UNSIGNED_SHORT_5_5_5_1; break;
		case Image::ePDT_USHORT_565: dataType = GL_UNSIGNED_SHORT_5_6_5; break;
		}


		tBYTE* ptr = (tBYTE*)data;
		while( 0 != (mipmaps + 1) )
		{
			//mipmap loading

			//if( compressed )
			//	glCompressedTexImage2D( GL_TEXTURE_2D, level, dataFormat, width, height, 0, (width*height*bpp) / 8, (const void*)ptr );
			//else
				glTexImage2D( GL_TEXTURE_2D, level, dataFormat, width, height, 0, dataFormat, dataType, (const void*)ptr );

			// calculate offset
			ptr += (width * height * bpp) / 8;
			width /= 2;
			height /= 2;

			level++;
			mipmaps--;
		}

		glBindTexture( GL_TEXTURE_2D, 0 );
		return tex;
	}
	
	//=================================================================================================================
	// Summary: bind a texture
	// @textureID: texture name to bound
	//=================================================================================================================
	tBOOL driver::bindTexture(tU32 textureID)
	{
		glBindTexture( GL_TEXTURE_2D, textureID );
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
		return TRUE;
	}
	
	//=================================================================================================================
	// Summary: unbind a texture
	//=================================================================================================================
	void driver::unBindTexture( void )
	{
		glBindTexture( GL_TEXTURE_2D, 0 );
	}
	

}
}